'''
Module: ep_grammar.py
Purpose: Provide Grammar handling for ep_core.
Author: Avishay Alon
'''
#########################################
### Imports
#########################################
from collections import defaultdict

#########################################
### Classes
#########################################
class Terminal(str):
    ''' Wrapper for distinguishing between terminal and non-terminal rules. '''
    pass

class Rule(object):
    ''' Contains head (str) and production (str).
        Assumes that a terminal rule will be initiated 
        with a production parameter of class Terminal. '''
    def __init__(self, head, production):
        self._head = head
        self._production = production
        self._is_terminal = isinstance(production, Terminal)

    def __str__(self):
        return 'Rule: %s -> %s, length: %d' % (
            self._head, 
            self._production, 
            len(self))
            
    def __repr__(self):
        return '%s -> %s' % (self._head, self._production, ) 

    def __len__(self):
        return len(self._production.split())

    def __eq__(self, other):
        return (self._head == other.head and 
                    self._production == other.production)
    def __ne__(self, other):
        return not (self == other)

    head = property(lambda self: self._head)
    production = property(lambda self: self._production)
    is_terminal = property(lambda self: self._is_terminal)

class Grammar(object):
    ''' This class is used to parse the grammar file, 
        process its various rules and symbols,
        and provide simple interface for use in the EarleyParser class. '''
    def __init__(self, grammar_file):
        self._rules = defaultdict(set)
        self._terminals = set()
        self._nonterminals = set()
        self._start_symbol = None
        self._load_from_file(grammar_file)
        self._rule_set = set()
        self._terminal_rules = set()

        for rule_set in self._rules.values():
            for rule in rule_set:
                self._rule_set.add(rule)

        for rule in self._rule_set:
            if isinstance(rule.production, Terminal):
                self._terminal_rules.add(rule)

        self._nonterminal_rules = self._rule_set.difference(self._terminal_rules)

    def _load_from_file(self, grammar_filepath):
        ''' This method loads a grammar file into the grammar object.
            Assumes normal form and specific formatting of HEAD -> BODY. 
            The head of the first rule is the start symbol. '''
        with open(grammar_filepath, 'rb') as grammar_file:
            rules = [ line.strip() for line in grammar_file.readlines() if line.strip() ]

        ### Process rules
        for rule in rules:
            head, body = [ element.strip() for element in rule.split('->') ]
            if not self._start_symbol:
                self._start_symbol = head
            self._nonterminals.add(head)
            ### Terminal rule
            if len(body.split()) == 1 and body.islower():
                self._terminals.add(body)
                self._rules[head].add(Rule(head, Terminal(body)))
            ### Non-terminal rule
            else:
                self._rules[head].add(Rule(head, body))

    def __str__(self):
        str_repr = '----------------------------------\n> Grammar:'
        for rule in self._rules[self._start_symbol]:
            str_repr = '%s\n%s' % (str_repr, rule, )
        for key, rule_set in self._rules.items():
            if key != self._start_symbol:
                for rule in rule_set:
                    str_repr = '%s\n%s' % (str_repr, rule, )
        str_repr = '%s\n----------------------------------' % (str_repr, )
        return str_repr

    terminals = property(lambda self: self._terminals)
    nonterminals = property(lambda self: self._nonterminals)
    rules = property(lambda self: self._rules)
    rule_set = property(lambda self: self._rule_set)
    start_symbol = property(lambda self: self._start_symbol)
    terminal_rules = property(lambda self: self._terminal_rules)
    nonterminal_rules = property(lambda self: self._nonterminal_rules)
